/*
 * @(#)XPath.java	1.0 Oct 22, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.xml;

import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.xml.namespace.*;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathConstants;
import javax.xml.XMLConstants;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.io.File;
import java.io.IOException;

/**
 * Derived from http://www.ibm.com/developerworks/library/x-javaxpathapi.html
 */
public class XPath
implements NamespaceContext
{
    private ConcurrentHashMap<String,String> ns;
    private File source;
    private String expression;
    private DocumentBuilderFactory domFactory;
    private DocumentBuilder builder;
    private XPathFactory factory;
    private javax.xml.xpath.XPath xpathEngine;

    /**
     * args[0] = catalog.xml
     * args[1] = "/catalog/journal/article[@date='January-2004']/title"
     * args[2+] = prefix=URL
     *
     * Example:
java XPathTest pom.xml "/mvn:project/mvn:version/text()" mvn=http://maven.apache.org/POM/4.0.0
1.0
     */
    public static void main(String ... args)
    {
        if(args.length < 2)
        {
            System.out.format("USAGE: java org.eoti.xml.XPath XML_FILE EXPRESSION [[ns1=uri1]]\n");
            System.out.format("EXAMPLE: java org.eoti.xml.XPath pom.xml \"/mvn:project/mvn:version/text()\" mvn=http://maven.apache.org/POM/4.0.0\n");
            System.exit(1);
        }

        try{
            XPath app = new XPath();
            for(int i=2; i<args.length; i++)
            {
                int idx = args[i].indexOf('=');
                if(idx < 1) continue;
                app.addNamespace(args[i].substring(0, idx), args[i].substring(idx+1));
            }
            app.setSource(args[0]);
            app.setExpression(args[1]);
            System.out.format("%s", app.evaluateAsString());
        }catch(Exception e){
            System.err.format("Exception: %s\n", e.getMessage());
            System.exit(3);
        }

    }

    public XPath()
            throws ParserConfigurationException
    {
        ns = new ConcurrentHashMap<String,String>();
        domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        builder = domFactory.newDocumentBuilder();
        factory = XPathFactory.newInstance();
        xpathEngine = factory.newXPath();
        xpathEngine.setNamespaceContext(this);
    }

    public NodeList evaluate()
            throws IOException, SAXException, XPathExpressionException
    {
        Document doc = builder.parse(source);
        XPathExpression expr = xpathEngine.compile(expression);
        return (NodeList) (expr.evaluate(doc, XPathConstants.NODESET));
    }

    public String evaluateAsString()
            throws IOException, SAXException, XPathExpressionException
    {
        NodeList nodes = evaluate();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < nodes.getLength(); i++)
            sb.append(String.format("%s\n", nodes.item(i).getNodeValue()));

        return sb.toString();
    }

    public void setExpression(String expr)
    {
        this.expression = expr;
    }

    public void setSource(String fileName)
    {
        setSource(new File(fileName));
    }

    public void setSource(File file)
    {
        this.source = file;
    }

    public void addNamespace(String prefix, String namespaceURI)
    {
        ns.put(prefix, namespaceURI);
    }

    public String getNamespaceURI(String prefix)
    {
        if(prefix == null) throw new NullPointerException("prefix can not be null");
        String uri = ns.get(prefix);
        if(uri == null) return XMLConstants.NULL_NS_URI;
        return uri;
    }

    public String getPrefix(String namespaceURI)
    {
        for(String prefix : ns.keySet())
        {
            if(namespaceURI.equals(ns.get(prefix)))
                return prefix;
        }
        return null;
    }

    public Iterator getPrefixes(String namespaceURI)
    {
        ArrayList<String> prefixes = new ArrayList<String>();
        for(String prefix : ns.keySet())
        {
            if(namespaceURI.equals(ns.get(prefix)))
                prefixes.add(prefix);
        }
        return prefixes.iterator();
    }
}
